"""
 针对不同的设备生成的报告信息做对应的处理,前端通过websocket进行数据交互
 根据患者的体检号获取到对应的报告信息
"""
import asyncio
import json
import threading
from configparser import ConfigParser
from json import JSONDecodeError
import os
import pyodbc
import websockets
from websockets.exceptions import ConnectionClosedError

import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# 读取配置文件中的配置信息
conf = ConfigParser()
conf.read('./conf.ini', encoding='utf-8')
# 体检号
examNo = ''
# 获取当前设备的类型信息
REPORT_TYPE = conf['base']['type']
# 是否开启监听文件夹变动
OPEN_WATCH_DIR = conf['base']['open_watch_dir']
# 需要监听的文件夹
WATCH_DIR = conf['base']['watch_dir']
# 是否支持向检测设备中插入患者信息
SET_INFO = conf['base']['set_info']
# 检查结果图片保存路径
PIC_DIR = conf['pic_dir']['dir']
# 文件列表
files = []
cursor = None
# 保存当前长链接session
CONNECTION_SET = set()
send_msg_loop = None  # 新开一个loop,用于在同步代码中调用异步方法


def connect_adb():
    """
        数据库连接信息
    :return:
    """
    global cursor
    if conf.has_option(section='sqlserver', option=''):
        """
            如果配置了sqlserver的连接信息,则这里进行链接
        """
        driver = conf['sqlserver']['driver']
        host = conf['sqlserver']['host']
        database = conf['sqlserver']['database']
        username = conf['sqlserver']['username']
        pwd = conf['sqlserver']['pwd']
        conn = pyodbc.connect(f'DRIVER={driver};SERVER={host};DATABASE={database};UID={username};PWD={pwd}')
        cursor = conn.cursor()

    if conf.has_option(section='access', option='database'):
        print('connection---')
        global table, pre_table
        """
            如果配置了access 的连接信息,则这里进行链接
        """
        driver = conf['access']['driver']
        database = conf['access']['database']
        table = conf['access']['table']
        pre_table = conf['access']['pre_table']
        try:
            con_str = r"DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};" + f"DBQ={database};"
            print(con_str)
            conn = pyodbc.connect(con_str)
            cursor = conn.cursor()
        except Exception as e:
            print("连接数据库出错:", e)


async def send_msg(msg):
    """
        发送消息给前端
    :param msg: 需要发送的json数据
    :return:
    """
    if CONNECTION_SET:
        for c in CONNECTION_SET:
            await c.send(json.dumps(msg))


async def select_info_by_no(no):
    global cursor
    """
        根据体检号查询报告信息
    :param no: 体检号
    :return: 报告信息,可以为None
    """
    if cursor:
        cursor.execute(f'select * from {table} where 编号={no}')
        r = cursor.fetchone()
        return r
    else:
        return None


async def find_pic_by_dir(websocket):
    """
    在指定的文件夹中获取文件列表
    :param websocket:
    :return:
    """
    global files
    files = os.listdir(PIC_DIR)
    print('files:', files)
    if files:
        result = list(filter(lambda p: examNo in p, files))
        if result:
            pic = ''
            if len(result) > 1:
                for p in result:
                    pic += ',' + os.path.join(PIC_DIR, p)
                pic = pic[1:]
            else:
                pic = PIC_DIR + '/' + result[0]
            await websocket.send(json.dumps({"examNo": examNo, "type": 4, "pic": pic}))
        else:
            await websocket.send(json.dumps({"examNo": examNo, "type": 4}))
    else:
        await websocket.send(json.dumps({"examNo": examNo, "type": 4}))


async def dealReport(websocket, type_exam):
    """
    根据[type_exam] 不同类型进行对应的操作
    :param websocket:
    :param type_exam:
    :param exam_no:
    :return:
    """
    if type_exam == 4:
        if PIC_DIR:
            await find_pic_by_dir(websocket)
    else:
        pass


async def insert_info_to_db(data):
    """
        将用户信息插入到本地检测系统的数据库中
    :param data: 用户信息
    :return:
    """

    if data and cursor:
        regNo = data['registNo']

        select_sql = f"select * from {pre_table} where 编号='{regNo}'"

        cursor.execute(select_sql)
        r = cursor.fetchone()
        print("查询结果:", r)
        if not r:
            insert_sql = (f"insert into {pre_table} ([日期],[性别],[年龄],[姓名],[编号])"
                          f"values ('{time.strftime('%Y/%m/%d %H:%M:%S', time.localtime())}','{'男' if data['sex'] == 1 else '女'}','{data['age']}','{data['patientName']}','{regNo}')")
            print("insert:", insert_sql)
            cursor.execute(insert_sql)
            cursor.commit()


async def register(websocket, *args):
    """
    监听前端消息
    :param websocket:
    :param args:
    :return:
    """
    global examNo
    CONNECTION_SET.add(websocket)
    try:
        async for message in websocket:
            try:
                event = json.loads(message)
                print('json:', event)
                type_exam = event['type']
                print(type_exam)
                if type_exam == 'ping':
                    await websocket.send(json.dumps({"type": "pong"}))

                elif type_exam == 'info':
                    # 如果前端发送患者信息,则判断当前是否能够进行数据的插入
                    if SET_INFO == '1' and 'info' in event:
                        await insert_info_to_db(event['info'])

                elif type_exam == 4:
                    # 根据体检号查找体检报告图片
                    examNo = event['examNo']
                    await find_pic_by_dir(websocket)
                else:
                    await websocket.send('{"type":-1,"data":"undefined"}')
            except JSONDecodeError as e:
                print('解析出错:', e)
            except KeyError as e:
                print('没有对应的key值', e)
            except ConnectionClosedError as e:
                print('error:', e)
    finally:
        # Unregister user
        CONNECTION_SET.remove(websocket)
        await websocket.send('close')


class MyHandler(FileSystemEventHandler):
    def on_modified(self, event):
        print(f"文件 {event.src_path} 被修改")

    def on_created(self, event):
        """
            判断当前新增的文件是否为图片,如果是,则去数据库中查询最新一条数据
        :param event:
        :return:
        """
        global cursor, send_msg_loop
        if event.src_path:
            type_str = event.src_path.split('.')[-1].lower()
            if type_str == 'jpeg' or type_str == 'jpg':
                print('图片文件', cursor)
                if cursor:
                    cursor.execute(f'select top 1 * from {table} order by 索引 desc')
                    r = cursor.fetchone()
                    print("查询结果:", r)
                    if r:
                        no = r[5]  # 体检号
                        value = r[6]
                        result = r[7]  # 结果,阴性-,阳性+
                    try:
                        coroutine = send_msg(
                            {"examNo": no, "type": REPORT_TYPE, "data": {"value": value, "result": result},
                             "pic": event.src_path})
                        if send_msg_loop is None:
                            send_msg_loop = asyncio.new_event_loop()
                        asyncio.set_event_loop(send_msg_loop)
                        future = asyncio.gather(coroutine)
                        send_msg_loop.run_until_complete(future)
                    except Exception as e:
                        print("发送消息失败:", e)
        print(f"文件{event.src_path} 被新增")


async def watchDir():
    """
    监听文件夹变动情况
    :return:
    """
    print("开启监听文件夹变动")
    event_handler = MyHandler()
    observer = Observer()
    observer.schedule(event_handler, path=WATCH_DIR, recursive=False)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


def watch_dir_loop(loop):
    """
     开启一个新线程监听文件夹变动
    :param loop:
    :return:
    """
    print("开启loop")
    asyncio.set_event_loop(loop)
    loop.run_until_complete(asyncio.gather(watchDir()))


if __name__ == '__main__':
    connect_adb()
    if OPEN_WATCH_DIR == '1':
        thread_loop = asyncio.new_event_loop()
        t = threading.Thread(target=watch_dir_loop, args=(thread_loop,))
        t.daemon = True
        t.start()
    start_server = websockets.serve(register, 'localhost', 8765)
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.get_event_loop().run_forever()
